/*
 * $Id: sysapi_epoll.c,v 1.15 2005-12-28 14:46:55 bacon Exp $
 */

#include <xp/bas/sysapi.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

#define HANDLE_INVALID(p) (((p)->__handle) == -1)
#define HANDLE_VALID(p) (!HANDLE_INVALID(p))

xp_epoll_t* xp_epoll_open (xp_epoll_t* p, xp_size_t size)
{
	void* x;

	if (p == XP_NULL) {
		p = (xp_epoll_t*)xp_malloc (xp_sizeof(xp_epoll_t));
		if (p == XP_NULL) return XP_NULL;
		p->__dynamic = xp_true;
	}
	else p->__dynamic = xp_false;

#ifdef XP_HAVE_SYS_EPOLL_H
	p->__handle = epoll_create (size);
	if (HANDLE_INVALID(p) && errno != ENOSYS) {
		if (p->__dynamic) xp_free (p);
		return XP_NULL;
	}
#else
	p->__handle = -1;
#endif

	p->__capacity = size;
	p->__size = 0;
	p->revents = (xp_epoll_event_t*)
		xp_malloc (xp_sizeof(xp_epoll_event_t) * size);
	if (p->revents == XP_NULL) {
		if (p->__dynamic) xp_free (p);
		/* set errno to ENOMEM as it can be reset by xp_free above */
		xp_seterrno (XP_ENOMEM); 
		return XP_NULL;
	}

#ifdef XP_HAVE_SYS_EPOLL_H
	x = HANDLE_INVALID(p)?
		(void*)xp_malloc (xp_sizeof(xp_poll_t) * size):
		(void*)xp_malloc (xp_sizeof(struct epoll_event) * size);
#else
	xp_assert (HANDLE_INVALID(p));
	x = (void*) xp_malloc (xp_sizeof(xp_poll_t) * size);
#endif
	/* bp & ep are union members so either can be checked */
	if (x == XP_NULL) {
		xp_free (p->revents);
		if (p->__dynamic) xp_free (p);
		xp_seterrno (XP_ENOMEM);
		return XP_NULL;
	}

	if (HANDLE_INVALID(p))
		p->__events.bp = (xp_poll_t*)x;
#ifdef XP_HAVE_SYS_EPOLL_H
	else p->__events.ep = (struct epoll_event*)x;
#endif

	return p;
}

int xp_epoll_close (xp_epoll_t* p)
{
	if (HANDLE_VALID(p) && xp_close (p->__handle) == -1) return -1;
	xp_free (p->revents);
	xp_free (p->__events.bp);
	if (p->__dynamic) xp_free (p);
	return 0;
}

int xp_epoll_add (xp_epoll_t* p, int fd, int events)
{
	if (p->__size >= p->__capacity) {
		void* __e;
		xp_epoll_event_t* e;
		xp_size_t capa;
		/* double the capacity */
		capa = (p->__capacity << 1); /* TODO: a new increment scheme */

		e = (xp_epoll_event_t*)
			xp_malloc (xp_sizeof(xp_epoll_event_t) * capa);
		if (e == XP_NULL) return -1;

#ifdef XP_HAVE_SYS_EPOLL_H
		__e = HANDLE_INVALID(p)?
			(void*)xp_realloc (p->__events.bp, xp_sizeof(xp_poll_t) * capa):
			(void*)xp_malloc (xp_sizeof(struct epoll_event) * capa);
#else
		__e = (void*)xp_realloc (p->__events.bp, xp_sizeof(xp_poll_t) * capa);
#endif
		if (__e == XP_NULL) {
			xp_free (e);
			xp_seterrno (XP_ENOMEM);
			return -1;
		}

		xp_free (p->revents);
#ifdef XP_HAVE_SYS_EPOLL_H
		if (p->__handle != -1) xp_free (p->__events.ep);
#endif

		p->revents = e;
		if (HANDLE_INVALID(p))
			p->__events.bp = (xp_poll_t*)__e;
#ifdef XP_HAVE_SYS_EPOLL_H
		else p->__events.ep = (struct epoll_event*)__e;
#endif
		p->__capacity = capa;
	}

	if (HANDLE_INVALID(p)) {
		p->__events.bp[p->__size].fd = fd;
		p->__events.bp[p->__size].events = events;

	}
#ifdef XP_HAVE_SYS_EPOLL_H
	else {
		struct epoll_event ev;
		ev.events = events;
		ev.data.fd = fd;
		if (epoll_ctl (p->__handle, EPOLL_CTL_ADD, fd, &ev) == -1) return -1;
	}
#endif

	p->__size++;
	return 0;
}

int xp_epoll_remove (xp_epoll_t* p, int fd, int events)
{
	if (p->__size <= 0) return -1;

	if (HANDLE_INVALID(p)) {
		xp_size_t i;

		/* TODO: is this correct? */
		for (i = 0; i < p->__size; i++) {
			if (p->__events.bp[i].fd == fd &&
			    p->__events.bp[i].events == events) {
				xp_memcpy (
					&p->__events.bp[i], &p->__events.bp[i + 1], 
					xp_sizeof(p->__events.bp[0]) * (p->__size - i - 1));
				break;
			}
		}

		if (i >= p->__size) return -1;
	}
#ifdef XP_HAVE_SYS_EPOLL_H
	else {
		struct epoll_event ev;
		ev.events = events;
		ev.data.fd = fd;
		if (epoll_ctl (p->__handle, EPOLL_CTL_DEL, fd, &ev) == -1) return -1;
	}
#endif

	p->__size--;
	return 0;
}

xp_ssize_t xp_epoll_wait (xp_epoll_t* p, xp_time_t timeout)
{
	xp_ssize_t n = -1, i;

	if (HANDLE_INVALID(p)) {
		xp_ssize_t count = 0;

		n = xp_poll (p->__events.bp, p->__size, timeout);
		if (n <= 0) return n;

		for (i = 0; i < p->__size; i++) {
			if (p->__events.bp[i].revents > 0) {
				p->revents[count].events = p->__events.bp[i].revents;
				p->revents[count].handle = p->__events.bp[i].fd;
				count++;
			}
		}
		xp_assert (count == n);
	}
#ifdef XP_HAVE_SYS_EPOLL_H
	else {
		n = epoll_wait (p->__handle, p->__events.ep, p->__size, timeout);
		for (i = 0; i < n; i++) {
			p->revents->events = p->__events.ep->events;
			p->revents->handle = p->__events.ep->data.fd;
		}
	}
#endif

	return n;
}

